﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.IO;

namespace KMeansDemo
{
    class Program
    {
        class TextVector
        {
            public int group = -1;
            public string fileName = string.Empty;
            public double[] freq = null;
        }



        static TextVector[] ConvertTextDbToVectorDb(string path, out int maxFreq)
        {
            Hashtable ht = new Hashtable();
            string[] fileList = Directory.GetFiles(path);
            TextVector[] tvList = new TextVector[fileList.Length];
            int i = 0, j = 0;
            maxFreq = 0;

            for (i=0;i<fileList.Length;i++)
            {
                string fileName = fileList[i];
                
                TextVector tv = new TextVector();
                tv.fileName = fileName;
                tv.group = -1;

                tvList[i] = tv;

                string data = File.ReadAllText(fileName);
                string[] wordList = data.Split(' ');

                foreach (string word in wordList)
                {
                    if (word.Length == 0)
                        continue;

                    int[] freq = (int[])ht[word];

                    if (freq != null)
                    {
                        (freq[i])++;
                    }
                    else
                    {
                        freq = new int[fileList.Length];
                        for (j = 0; j < freq.Length; j++)
                        {
                            freq[j] = 0;
                        }

                        freq[i] = 1;
                        ht.Add(word, freq);

                    }

                }
            }

            for (i = 0; i < fileList.Length; i++)
            {
                TextVector tv = tvList[i];
                double[] freq2 = new double[ht.Count];
                j = 0;

                foreach (DictionaryEntry de in ht)
                {
                    int[] freq = (int[])de.Value;
                    int num = freq[i];

                    freq2[j++] = (double)num;
                    if (num > maxFreq)
                    {
                        maxFreq = num;
                    }

                }

                tv.freq = freq2;
            }

            return tvList;

        }


        static void KMeans(TextVector[] tvList, int k, int maxFreq, string outputPath)
        {
            if (tvList.Length < k || k < 1)
                return;
            
            int vectorSize = tvList[0].freq.Length;
            double [][] arCenter = new double[k][];
            Random random = new Random();
            int i = 0, j = 0;

            // Random center
            for (i = 0; i < k; i++)
            {
                arCenter[i] = new double[vectorSize];
                for (j = 0; j < vectorSize; j++)
                {
                    arCenter[i][j] = (double)((int)Math.Abs(random.Next()) % (maxFreq + 1));
                }
            }


            bool bGroupChanged = false;


            do
            {
                // Grouping
                bGroupChanged = false;

                for (i = 0; i < tvList.Length; i++)
                {
                    TextVector tv = tvList[i];
                    int oldGroup = tv.group;
                    int newGroup = FindSuitableGroup(tv.freq, arCenter);
                    tv.group = newGroup;
                    bGroupChanged |= (oldGroup != newGroup);
                }

                if (!bGroupChanged)
                    break;

                // Renew all centers
                for (i = 0; i < k; i++)
                {
                    //ResetVector(arCenter[i]);
                    
                    for (j = 0; j < tvList.Length; j++)
                    {
                        TextVector tv = tvList[i];
                        AddVector(arCenter[i], tv.freq);
                    }

                    AvgVector(arCenter[i]);
                }

            }
            while (true);

            for (i = 0; i < tvList.Length; i++)
            {
                TextVector tv = tvList[i];
                FileInfo fi = new FileInfo(tv.fileName);
                string newFileName = outputPath + "\\group_";
                newFileName += tv.group.ToString();

                if (!Directory.Exists(newFileName))
                {
                    Directory.CreateDirectory(newFileName);
                }

                newFileName += "\\" + fi.Name;
                File.Copy(tv.fileName, newFileName);
            }


        }

        static void AvgVector(double[] vector)
        {
            for (int i = 0; i < vector.Length; i++)
            {
                vector[i] /= vector.Length;
            }
        }

        static void AddVector(double[] vector1, double[] vector2)
        {
            for (int i = 0; i < vector1.Length; i++)
            {
                vector1[i] += vector2[i];
            }
        }

        static void ResetVector(double[] vector)
        {
            for (int i = 0; i < vector.Length; i++)
            {
                vector[i] = 0;
            }
        }

        static int FindSuitableGroup(double[] vector, double[][] arCenter)
        {
            int group = -1;
            double dMin = -1;

            for (int i = 0; i < arCenter.Length; i++)
            {
                double d = Euclid(vector, arCenter[i]);
                if (dMin < 0 || d < dMin)
                {
                    dMin = d;
                    group = i;
                }
            }

            return group;
        }

        static double Euclid(double[] vector1, double[] vector2)
        {
            double result = 0.0;
            
            for (int i = 0; i < vector1.Length; i++)
            {
                result += (Math.Pow((vector1[i] - vector2[i]), 2));
            }

            return Math.Sqrt(result);
        }


        static void Main(string[] args)
        {
            string inputPath = "m:\\docs";
            string outputPath = "m:\\groups";
            int k = 3;

            int maxFreq = 0;
            TextVector[] vectorList = ConvertTextDbToVectorDb(inputPath, out maxFreq);
            KMeans(vectorList, k, maxFreq, outputPath);

        }
    }
}
